<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Q版迪士尼俄罗斯方块</title>
  <style>
    body {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
      background-color: #f0f8ff;
      /* AliceBlue, a light Disney-like blue */
      font-family: 'Comic Sans MS', cursive, sans-serif;
      /* Q-version font */
      margin: 0;
      overflow: hidden;
      /* Prevent scrollbars */
    }

    #game-container {
      display: flex;
      align-items: flex-start;
      gap: 20px;
    }

    #game-board {
      width: 300px;
      /* 10 cells * 30px */
      height: 600px;
      /* 20 cells * 30px */
      background-color: #add8e6;
      /* LightBlue */
      border: 5px solid #4682b4;
      /* SteelBlue border */
      position: relative;
      box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.3);
      border-radius: 10px;
      overflow: hidden;
      /* Keep blocks within bounds */
    }

    .cell {
      width: 30px;
      height: 30px;
      position: absolute;
      box-sizing: border-box;
      border: 1px solid rgba(0, 0, 0, 0.1);
      border-radius: 5px;
      /* Rounded corners for Q-style */
      /* 3D effect */
      box-shadow: inset 2px 2px 5px rgba(255, 255, 255, 0.5),
        inset -2px -2px 5px rgba(0, 0, 0, 0.2),
        2px 2px 5px rgba(0, 0, 0, 0.3);
      transition: transform 0.1s ease-out, box-shadow 0.1s ease-out;
    }

    /* Define colors for different Tetromino shapes (Disney-inspired palette) */
    .I {
      background-color: #1e90ff;
    }

    /* DodgerBlue */
    .O {
      background-color: #ffd700;
    }

    /* Gold */
    .T {
      background-color: #9370db;
    }

    /* MediumPurple */
    .S {
      background-color: #32cd32;
    }

    /* LimeGreen */
    .Z {
      background-color: #ff6347;
    }

    /* Tomato */
    .J {
      background-color: #4682b4;
    }

    /* SteelBlue */
    .L {
      background-color: #ff8c00;
    }

    /* DarkOrange */

    #score-panel {
      display: flex;
      flex-direction: column;
      align-items: center;
      padding: 20px;
      background-color: #fffacd;
      /* LemonChiffon */
      border: 5px solid #ffdab9;
      /* PeachPuff */
      border-radius: 10px;
      box-shadow: 3px 3px 10px rgba(0, 0, 0, 0.2);
    }

    #score-panel h2 {
      margin-top: 0;
      color: #ff69b4;
      /* HotPink */
    }

    #score {
      font-size: 2em;
      font-weight: bold;
      color: #ff1493;
      /* DeepPink */
    }

    #next-piece-container {
      margin-top: 20px;
      text-align: center;
    }

    #next-piece-board {
      width: 120px;
      /* 4 cells * 30px */
      height: 120px;
      /* 4 cells * 30px */
      background-color: #e0ffff;
      /* LightCyan */
      border: 3px solid #afeeee;
      /* PaleTurquoise */
      position: relative;
      margin-top: 10px;
      border-radius: 5px;
    }

    .next-cell {
      width: 30px;
      height: 30px;
      position: absolute;
      box-sizing: border-box;
      border: 1px solid rgba(0, 0, 0, 0.1);
      border-radius: 5px;
      box-shadow: inset 1px 1px 3px rgba(255, 255, 255, 0.5),
        inset -1px -1px 3px rgba(0, 0, 0, 0.2),
        1px 1px 3px rgba(0, 0, 0, 0.2);
    }

    /* Simple animation for landing */
    .landing {
      transform: scale(1.05);
      box-shadow: inset 3px 3px 7px rgba(255, 255, 255, 0.6),
        inset -3px -3px 7px rgba(0, 0, 0, 0.3),
        3px 3px 7px rgba(0, 0, 0, 0.4);
    }

    /* Style for game over */
    #game-over {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      background-color: rgba(0, 0, 0, 0.7);
      color: white;
      padding: 20px 40px;
      border-radius: 15px;
      font-size: 2em;
      text-align: center;
      display: none;
      /* Hidden by default */
      z-index: 100;
      text-shadow: 2px 2px 4px #000;
    }
  </style>
</head>

<body>
  <div id="game-container">
    <div id="game-board">
      <!-- Cells will be added dynamically by JavaScript -->
      <div id="game-over">Game Over!</div>
    </div>
    <div id="score-panel">
      <h2>分数</h2>
      <div id="score">0</div>
      <div id="next-piece-container">
        <h3>下一个</h3>
        <div id="next-piece-board">
          <!-- Next piece preview -->
        </div>
      </div>
    </div>
  </div>

  <script>
    const BOARD_WIDTH = 10;
    const BOARD_HEIGHT = 20;
    const CELL_SIZE = 30; // Matches CSS

    const gameBoard = document.getElementById('game-board');
    const scoreDisplay = document.getElementById('score');
    const nextPieceBoard = document.getElementById('next-piece-board');
    const gameOverDisplay = document.getElementById('game-over');

    // Set board dimensions based on constants
    gameBoard.style.width = `${BOARD_WIDTH * CELL_SIZE}px`;
    gameBoard.style.height = `${BOARD_HEIGHT * CELL_SIZE}px`;
    nextPieceBoard.style.width = `${4 * CELL_SIZE}px`; // Typically 4x4 grid for preview
    nextPieceBoard.style.height = `${4 * CELL_SIZE}px`;

    let board = Array.from({ length: BOARD_HEIGHT }, () => Array(BOARD_WIDTH).fill(0));
    let score = 0;
    let currentPiece;
    let currentX, currentY;
    let nextPiece;
    let gameLoopInterval;
    let isGameOver = false;

    // Tetromino shapes and their colors (matches CSS classes)
    const TETROMINOS = {
      'I': { shape: [[1, 1, 1, 1]], color: 'I' },
      'O': { shape: [[1, 1], [1, 1]], color: 'O' },
      'T': { shape: [[0, 1, 0], [1, 1, 1]], color: 'T' },
      'S': { shape: [[0, 1, 1], [1, 1, 0]], color: 'S' },
      'Z': { shape: [[1, 1, 0], [0, 1, 1]], color: 'Z' },
      'J': { shape: [[1, 0, 0], [1, 1, 1]], color: 'J' },
      'L': { shape: [[0, 0, 1], [1, 1, 1]], color: 'L' }
    };
    const PIECE_KEYS = Object.keys(TETROMINOS);

    function getRandomPiece () {
      const key = PIECE_KEYS[Math.floor(Math.random() * PIECE_KEYS.length)];
      return JSON.parse(JSON.stringify(TETROMINOS[key])); // Deep copy
    }

    function drawBoard () {
      gameBoard.innerHTML = '<div id="game-over">Game Over!</div>'; // Clear previous state but keep game over message
      if (isGameOver) {
        gameOverDisplay.style.display = 'block';
      } else {
        gameOverDisplay.style.display = 'none';
      }


      // Draw settled pieces
      for (let y = 0; y < BOARD_HEIGHT; y++) {
        for (let x = 0; x < BOARD_WIDTH; x++) {
          if (board[y][x]) {
            const cell = document.createElement('div');
            cell.classList.add('cell', board[y][x]); // Use color class
            cell.style.left = `${x * CELL_SIZE}px`;
            cell.style.top = `${y * CELL_SIZE}px`;
            gameBoard.appendChild(cell);
          }
        }
      }

      // Draw current piece
      if (currentPiece && !isGameOver) {
        drawPiece(currentPiece, currentX, currentY, gameBoard, 'cell');
      }
    }

    function drawPiece (piece, xOffset, yOffset, targetBoard, cellClass) {
      piece.shape.forEach((row, y) => {
        row.forEach((value, x) => {
          if (value) {
            const cell = document.createElement('div');
            cell.classList.add(cellClass, piece.color);
            cell.style.left = `${(xOffset + x) * CELL_SIZE}px`;
            cell.style.top = `${(yOffset + y) * CELL_SIZE}px`;
            targetBoard.appendChild(cell);
          }
        });
      });
    }

    function drawNextPiece () {
      nextPieceBoard.innerHTML = ''; // Clear previous preview
      if (nextPiece) {
        // Center the piece in the preview box
        const pieceWidth = nextPiece.shape[0].length;
        const pieceHeight = nextPiece.shape.length;
        const offsetX = Math.floor((4 - pieceWidth) / 2);
        const offsetY = Math.floor((4 - pieceHeight) / 2);
        drawPiece(nextPiece, offsetX, offsetY, nextPieceBoard, 'next-cell');
      }
    }


    function isValidMove (piece, x, y) {
      for (let row = 0; row < piece.shape.length; row++) {
        for (let col = 0; col < piece.shape[row].length; col++) {
          if (piece.shape[row][col]) {
            const boardX = x + col;
            const boardY = y + row;

            // Check boundaries
            if (boardX < 0 || boardX >= BOARD_WIDTH || boardY >= BOARD_HEIGHT) {
              return false;
            }
            // Check collision with settled pieces (only if y >= 0)
            if (boardY >= 0 && board[boardY][boardX]) {
              return false;
            }
          }
        }
      }
      return true;
    }

    function rotatePiece (piece) {
      const shape = piece.shape;
      const N = shape.length;
      const M = shape[0].length;
      const newShape = Array.from({ length: M }, () => Array(N).fill(0));

      for (let y = 0; y < N; y++) {
        for (let x = 0; x < M; x++) {
          newShape[x][N - 1 - y] = shape[y][x];
        }
      }
      piece.shape = newShape;
    }

    function handleRotate () {
      if (isGameOver) return;
      const originalShape = JSON.parse(JSON.stringify(currentPiece.shape)); // Store original
      rotatePiece(currentPiece);

      if (!isValidMove(currentPiece, currentX, currentY)) {
        // If invalid, try wall kicks (simple version: just move left/right)
        if (isValidMove(currentPiece, currentX + 1, currentY)) {
          currentX++;
        } else if (isValidMove(currentPiece, currentX - 1, currentY)) {
          currentX--;
        } else {
          // If still invalid, revert rotation
          currentPiece.shape = originalShape;
        }
      }
      drawBoard(); // Redraw after potential move/rotation
    }


    function mergePiece () {
      currentPiece.shape.forEach((row, y) => {
        row.forEach((value, x) => {
          if (value) {
            const boardX = currentX + x;
            const boardY = currentY + y;
            if (boardY >= 0) { // Ensure we don't write above the board
              board[boardY][boardX] = currentPiece.color;
            }
          }
        });
      });
      // Add landing animation effect (optional)
      addLandingEffect(currentX, currentY, currentPiece);
    }

    function addLandingEffect (startX, startY, piece) {
      piece.shape.forEach((row, y) => {
        row.forEach((value, x) => {
          if (value) {
            const boardX = startX + x;
            const boardY = startY + y;
            const cellSelector = `.cell[style*="left: ${boardX * CELL_SIZE}px; top: ${boardY * CELL_SIZE}px;"]`;
            const cellElement = gameBoard.querySelector(cellSelector);
            if (cellElement) {
              cellElement.classList.add('landing');
              setTimeout(() => cellElement.classList.remove('landing'), 150); // Remove after animation
            }
          }
        });
      });
    }


    function clearLines () {
      let linesCleared = 0;
      for (let y = BOARD_HEIGHT - 1; y >= 0; y--) {
        if (board[y].every(cell => cell !== 0)) {
          // Line is full
          linesCleared++;
          // Remove the line and shift everything above it down
          for (let row = y; row > 0; row--) {
            board[row] = board[row - 1];
          }
          board[0] = Array(BOARD_WIDTH).fill(0); // Add new empty line at the top
          y++; // Re-check the current line index as it now contains the line from above
        }
      }

      if (linesCleared > 0) {
        // Update score (simple scoring)
        score += linesCleared * 100 * linesCleared; // Bonus for multiple lines
        scoreDisplay.textContent = score;
        // Potentially increase speed here
      }
    }

    function spawnNewPiece () {
      currentPiece = nextPiece;
      nextPiece = getRandomPiece();
      currentX = Math.floor(BOARD_WIDTH / 2) - Math.floor(currentPiece.shape[0].length / 2);
      currentY = 0; // Start at the top

      if (!isValidMove(currentPiece, currentX, currentY)) {
        // Game Over condition
        isGameOver = true;
        clearInterval(gameLoopInterval);
        gameOverDisplay.style.display = 'block';
        console.log("Game Over!");
      }
      drawNextPiece(); // Update the preview
    }

    function moveDown () {
      if (isGameOver) return;
      if (isValidMove(currentPiece, currentX, currentY + 1)) {
        currentY++;
      } else {
        // Piece has landed
        mergePiece();
        clearLines();
        spawnNewPiece();
      }
      drawBoard();
    }

    function moveLeft () {
      if (isGameOver) return;
      if (isValidMove(currentPiece, currentX - 1, currentY)) {
        currentX--;
        drawBoard();
      }
    }

    function moveRight () {
      if (isGameOver) return;
      if (isValidMove(currentPiece, currentX + 1, currentY)) {
        currentX++;
        drawBoard();
      }
    }

    function dropPiece () {
      if (isGameOver) return;
      while (isValidMove(currentPiece, currentX, currentY + 1)) {
        currentY++;
      }
      moveDown(); // Trigger merge, clear, spawn
    }


    function gameLoop () {
      moveDown();
    }

    function startGame () {
      board = Array.from({ length: BOARD_HEIGHT }, () => Array(BOARD_WIDTH).fill(0));
      score = 0;
      scoreDisplay.textContent = score;
      isGameOver = false;
      gameOverDisplay.style.display = 'none';
      nextPiece = getRandomPiece(); // Initialize next piece
      spawnNewPiece(); // Spawn the first piece
      drawBoard();
      drawNextPiece();

      clearInterval(gameLoopInterval); // Clear any existing interval
      gameLoopInterval = setInterval(gameLoop, 500); // Game speed: lower value = faster (e.g., 800ms)
    }

    // Keyboard controls
    document.addEventListener('keydown', (event) => {
      if (isGameOver) return;

      switch (event.key) {
        case 'ArrowLeft':
        case 'a': // WASD controls
          moveLeft();
          break;
        case 'ArrowRight':
        case 'd':
          moveRight();
          break;
        case 'ArrowDown':
        case 's':
          moveDown();
          // Reset interval timer to prevent immediate drop after manual down move
          clearInterval(gameLoopInterval);
          gameLoopInterval = setInterval(gameLoop, 800); // Match the game speed interval
          break;
        case 'ArrowUp':
        case 'w': // Rotate
          handleRotate();
          break;
        case ' ': // Space bar for hard drop
          dropPiece();
          break;
      }
    });

    // Start the game when the page loads
    startGame();

  </script>
</body>

</html>